// Last time updated: 2019-06-24 6:39:09 PM UTC

// ________________________
// MultiStreamsMixer v1.2.3

// Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer

// --------------------------------------------------
// Muaz Khan     - www.MuazKhan.com
// MIT License   - www.WebRTC-Experiment.com/licence
// --------------------------------------------------

function MultiStreamsMixer(arrayOfMediaStreams, elementClass) {
  var browserFakeUserAgent =
    "Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45";

  (function(that) {
    if (typeof RecordRTC !== "undefined") {
      return;
    }

    if (!that) {
      return;
    }

    if (typeof window !== "undefined") {
      return;
    }

    if (typeof global === "undefined") {
      return;
    }

    global.navigator = {
      userAgent: browserFakeUserAgent,
      getUserMedia: function() {}
    };

    if (!global.console) {
      global.console = {};
    }

    if (
      typeof global.console.log === "undefined" ||
      typeof global.console.error === "undefined"
    ) {
      global.console.error = global.console.log =
        global.console.log ||
        function() {
          console.log(arguments);
        };
    }

    if (typeof document === "undefined") {
      /*global document:true */
      that.document = {
        documentElement: {
          appendChild: function() {
            return "";
          }
        }
      };

      document.createElement = document.captureStream = document.mozCaptureStream = function() {
        var obj = {
          getContext: function() {
            return obj;
          },
          play: function() {},
          pause: function() {},
          drawImage: function() {},
          toDataURL: function() {
            return "";
          },
          style: {}
        };
        return obj;
      };

      that.HTMLVideoElement = function() {};
    }

    if (typeof location === "undefined") {
      /*global location:true */
      that.location = {
        protocol: "file:",
        href: "",
        hash: ""
      };
    }

    if (typeof screen === "undefined") {
      /*global screen:true */
      that.screen = {
        width: 0,
        height: 0
      };
    }

    if (typeof URL === "undefined") {
      /*global screen:true */
      that.URL = {
        createObjectURL: function() {
          return "";
        },
        revokeObjectURL: function() {
          return "";
        }
      };
    }

    /*global window:true */
    that.window = global;
  })(typeof global !== "undefined" ? global : null);

  // requires: chrome://flags/#enable-experimental-web-platform-features

  elementClass = elementClass || "multi-streams-mixer";

  var videos = [];
  var isStopDrawingFrames = false;

  var canvas = document.createElement("canvas");
  var context = canvas.getContext("2d");
  canvas.style.opacity = 0;
  canvas.style.position = "absolute";
  canvas.style.zIndex = -1;
  canvas.style.top = "-1000em";
  canvas.style.left = "-1000em";
  canvas.className = elementClass;
  (document.body || document.documentElement).appendChild(canvas);

  this.disableLogs = false;
  this.frameInterval = 10;

  this.width = 360;
  this.height = 240;

  // use gain node to prevent echo
  this.useGainNode = true;

  var self = this;

  // _____________________________
  // Cross-Browser-Declarations.js

  // WebAudio API representer
  var AudioContext = window.AudioContext;

  if (typeof AudioContext === "undefined") {
    if (typeof webkitAudioContext !== "undefined") {
      /*global AudioContext:true */
      AudioContext = webkitAudioContext;
    }

    if (typeof mozAudioContext !== "undefined") {
      /*global AudioContext:true */
      AudioContext = mozAudioContext;
    }
  }

  /*jshint -W079 */
  var URL = window.URL;

  if (typeof URL === "undefined" && typeof webkitURL !== "undefined") {
    /*global URL:true */
    URL = webkitURL;
  }

  if (
    typeof navigator !== "undefined" &&
    typeof navigator.getUserMedia === "undefined"
  ) {
    // maybe window.navigator?
    if (typeof navigator.webkitGetUserMedia !== "undefined") {
      navigator.getUserMedia = navigator.webkitGetUserMedia;
    }

    if (typeof navigator.mozGetUserMedia !== "undefined") {
      navigator.getUserMedia = navigator.mozGetUserMedia;
    }
  }

  var MediaStream = window.MediaStream;

  if (
    typeof MediaStream === "undefined" &&
    typeof webkitMediaStream !== "undefined"
  ) {
    MediaStream = webkitMediaStream;
  }

  /*global MediaStream:true */
  if (typeof MediaStream !== "undefined") {
    // override "stop" method for all browsers
    if (typeof MediaStream.prototype.stop === "undefined") {
      MediaStream.prototype.stop = function() {
        this.getTracks().forEach(function(track) {
          track.stop();
        });
      };
    }
  }

  var Storage = {};

  if (typeof AudioContext !== "undefined") {
    Storage.AudioContext = AudioContext;
  } else if (typeof webkitAudioContext !== "undefined") {
    Storage.AudioContext = webkitAudioContext;
  }

  function setSrcObject(stream, element) {
    if ("srcObject" in element) {
      element.srcObject = stream;
    } else if ("mozSrcObject" in element) {
      element.mozSrcObject = stream;
    } else {
      element.srcObject = stream;
    }
  }

  this.startDrawingFrames = function() {
    drawVideosToCanvas();
  };

  function drawVideosToCanvas() {
    if (isStopDrawingFrames) {
      return;
    }

    var videosLength = videos.length;

    var fullcanvas = false;
    var remaining = [];
    videos.forEach(function(video) {
      if (!video.stream) {
        video.stream = {};
      }

      if (video.stream.fullcanvas) {
        fullcanvas = video;
      } else {
        // todo: video.stream.active or video.stream.live to fix blank frames issues?
        remaining.push(video);
      }
    });

    if (fullcanvas) {
      canvas.width = fullcanvas.stream.width;
      canvas.height = fullcanvas.stream.height;
    } else if (remaining.length) {
      canvas.width =
        videosLength > 1 ? remaining[0].width * 2 : remaining[0].width;

      var height = 1;
      if (videosLength === 3 || videosLength === 4) {
        height = 2;
      }
      if (videosLength === 5 || videosLength === 6) {
        height = 3;
      }
      if (videosLength === 7 || videosLength === 8) {
        height = 4;
      }
      if (videosLength === 9 || videosLength === 10) {
        height = 5;
      }
      canvas.height = remaining[0].height * height;
    } else {
      canvas.width = self.width || 360;
      canvas.height = self.height || 240;
    }

    if (fullcanvas && fullcanvas instanceof HTMLVideoElement) {
      drawImage(fullcanvas);
    }

    remaining.forEach(function(video, idx) {
      drawImage(video, idx);
    });

    setTimeout(drawVideosToCanvas, self.frameInterval);
  }

  function drawImage(video, idx) {
    if (isStopDrawingFrames) {
      return;
    }

    var x = 0;
    var y = 0;
    var width = video.width;
    var height = video.height;

    if (idx === 1) {
      x = video.width;
    }

    if (idx === 2) {
      y = video.height;
    }

    if (idx === 3) {
      x = video.width;
      y = video.height;
    }

    if (idx === 4) {
      y = video.height * 2;
    }

    if (idx === 5) {
      x = video.width;
      y = video.height * 2;
    }

    if (idx === 6) {
      y = video.height * 3;
    }

    if (idx === 7) {
      x = video.width;
      y = video.height * 3;
    }

    if (typeof video.stream.left !== "undefined") {
      x = video.stream.left;
    }

    if (typeof video.stream.top !== "undefined") {
      y = video.stream.top;
    }

    if (typeof video.stream.width !== "undefined") {
      width = video.stream.width;
    }

    if (typeof video.stream.height !== "undefined") {
      height = video.stream.height;
    }

    context.drawImage(video, x, y, width, height);

    if (typeof video.stream.onRender === "function") {
      video.stream.onRender(context, x, y, width, height, idx);
    }
  }

  function getMixedStream() {
    isStopDrawingFrames = false;
    var mixedVideoStream = getMixedVideoStream();

    var mixedAudioStream = getMixedAudioStream();
    if (mixedAudioStream) {
      mixedAudioStream
        .getTracks()
        .filter(function(t) {
          return t.kind === "audio";
        })
        .forEach(function(track) {
          mixedVideoStream.addTrack(track);
        });
    }

    var fullcanvas;
    arrayOfMediaStreams.forEach(function(stream) {
      if (stream.fullcanvas) {
        fullcanvas = true;
      }
    });

    // mixedVideoStream.prototype.appendStreams = appendStreams;
    // mixedVideoStream.prototype.resetVideoStreams = resetVideoStreams;
    // mixedVideoStream.prototype.clearRecordedData = clearRecordedData;

    return mixedVideoStream;
  }

  function getMixedVideoStream() {
    resetVideoStreams();

    var capturedStream;

    if ("captureStream" in canvas) {
      capturedStream = canvas.captureStream();
    } else if ("mozCaptureStream" in canvas) {
      capturedStream = canvas.mozCaptureStream();
    } else if (!self.disableLogs) {
      console.error(
        "Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features"
      );
    }

    var videoStream = new MediaStream();

    capturedStream
      .getTracks()
      .filter(function(t) {
        return t.kind === "video";
      })
      .forEach(function(track) {
        videoStream.addTrack(track);
      });

    canvas.stream = videoStream;

    return videoStream;
  }

  function getMixedAudioStream() {
    // via: @pehrsons
    if (!Storage.AudioContextConstructor) {
      Storage.AudioContextConstructor = new Storage.AudioContext();
    }

    self.audioContext = Storage.AudioContextConstructor;

    self.audioSources = [];

    if (self.useGainNode === true) {
      self.gainNode = self.audioContext.createGain();
      self.gainNode.connect(self.audioContext.destination);
      self.gainNode.gain.value = 0; // don't hear self
    }

    var audioTracksLength = 0;
    arrayOfMediaStreams.forEach(function(stream) {
      if (
        !stream.getTracks().filter(function(t) {
          return t.kind === "audio";
        }).length
      ) {
        return;
      }

      audioTracksLength++;

      var audioSource = self.audioContext.createMediaStreamSource(stream);

      if (self.useGainNode === true) {
        audioSource.connect(self.gainNode);
      }

      self.audioSources.push(audioSource);
    });

    self.audioDestination = self.audioContext.createMediaStreamDestination();
    self.audioSources.forEach(function(audioSource) {
      audioSource.connect(self.audioDestination);
    });
    return self.audioDestination.stream;
  }

  function getVideo(stream) {
    var video = document.createElement("video");

    setSrcObject(stream, video);

    video.className = elementClass;

    video.muted = true;
    video.volume = 0;

    video.width = stream.width || self.width || 360;
    video.height = stream.height || self.height || 240;

    video.play();

    return video;
  }

  this.appendStreams = function(streams) {
    if (!streams) {
      throw "First parameter is required.";
    }

    if (!(streams instanceof Array)) {
      streams = [streams];
    }

    streams.forEach(function(stream) {
      arrayOfMediaStreams.push(stream);

      var newStream = new MediaStream();

      if (
        stream.getTracks().filter(function(t) {
          return t.kind === "video";
        }).length
      ) {
        var video = getVideo(stream);
        video.stream = stream;
        videos.push(video);

        newStream.addTrack(
          stream.getTracks().filter(function(t) {
            return t.kind === "video";
          })[0]
        );
      }

      if (
        stream.getTracks().filter(function(t) {
          return t.kind === "audio";
        }).length
      ) {
        var audioSource = self.audioContext.createMediaStreamSource(stream);
        // self.audioDestination = self.audioContext.createMediaStreamDestination();
        audioSource.connect(self.audioDestination);

        newStream.addTrack(
          self.audioDestination.stream.getTracks().filter(function(t) {
            return t.kind === "audio";
          })[0]
        );
      }
    });
  };

  this.releaseStreams = function() {
    videos = [];
    isStopDrawingFrames = true;

    if (self.gainNode) {
      self.gainNode.disconnect();
      self.gainNode = null;
    }

    if (self.audioSources.length) {
      self.audioSources.forEach(function(source) {
        source.disconnect();
      });
      self.audioSources = [];
    }

    if (self.audioDestination) {
      self.audioDestination.disconnect();
      self.audioDestination = null;
    }

    if (self.audioContext) {
      self.audioContext.close();
    }

    self.audioContext = null;

    context.clearRect(0, 0, canvas.width, canvas.height);

    if (canvas.stream) {
      canvas.stream.stop();
      canvas.stream = null;
    }
  };

  this.resetVideoStreams = function(streams) {
    if (streams && !(streams instanceof Array)) {
      streams = [streams];
    }

    resetVideoStreams(streams);
  };

  function resetVideoStreams(streams) {
    videos = [];
    streams = streams || arrayOfMediaStreams;

    // via: @adrian-ber
    streams.forEach(function(stream) {
      if (
        !stream.getTracks().filter(function(t) {
          return t.kind === "video";
        }).length
      ) {
        return;
      }

      var video = getVideo(stream);
      video.stream = stream;
      videos.push(video);
    });
  }

  // for debugging
  this.name = "MultiStreamsMixer";
  this.toString = function() {
    return this.name;
  };

  this.getMixedStream = getMixedStream;
}

if (typeof RecordRTC === "undefined") {
  if (typeof module !== "undefined" /* && !!module.exports*/) {
    module.exports = MultiStreamsMixer;
  }

  if (typeof define === "function" && define.amd) {
    define("MultiStreamsMixer", [], function() {
      return MultiStreamsMixer;
    });
  }
}
